#!/bin/env python
# -*- coding: utf-8 -*-
'''
V4 update
1. interleave RTP packet with V3 msg into the same Encoder<->Muxer tcp
connection, see https://docs.google.com/Doc?docid=0AXib23EVPZ13ZGZrMjNjMzlfNzAyZm01a3RyY2c&hl=en


V 3.1 change from V3.0: add authentication

This is version 3 of mux svr. Differ fromm previous version by
1. encoder authentication mechanism
2. dynamically create channel as encoder/player request
3. talk verision 3 protocol http://docs.google.com/View?id=dfk23c39_477cp7ktcgh
4. msg header flag change to F3F3F3F3
5. encoder connection watchdog

@author: xwk
'''

import optparse, struct,sys,random,md5,time,platform,socket,os,sys
from datetime import datetime, timedelta

try:
    if platform.system()=="Linux":
        from twisted.internet import epollreactor
        epollreactor.install()
    from twisted.internet import reactor, protocol
    from twisted.python import log, logfile
    from twisted.internet.task import LoopingCall
    from twisted.internet.defer import Deferred
except Exception, e:
    print "Please install twisted module first (and/or win32api if on windows)\
	e.g. sudo yum intsall python-twisted-core.i386"
    print str(e)
    sys.exit()

import MySQLdb

RELEASE_TAG="Working Copy"
if platform.system()=="Linux":
    SDP_FILE_FOLDER="/var/dss/movies/ppclass_live"
    DSS_SVR_ADR = "127.0.0.1"
else:
    SDP_FILE_FOLDER="C:\\temp"
    DSS_SVR_ADR = "202.120.34.11"

WORK_MODE_PPCLASS = 0
#WORK_MODE_189Cast = 1

MUX_TO_DSS_RTP_PORT_RANGE_START = 21000
#SDP_FILE_FOLDER="."
AUDIO_RTP_INTERLEAVE_CH_ID=10
AUDIO_RTCP_INTERLEAVE_CH_ID=11
VIDEO_RTP_INTERLEAVE_CH_ID=12
VIDEO_RTCP_INTERLEAVE_CH_ID=13
SCREEN_RTP_INTERLEAVE_CH_ID=14
SCREEN_RTCP_INTERLEAVE_CH_ID=15

#Possible msg types
END_TO_END_SIGNALING_DATA_TYPE = 7 # mux svr will transparently relay this type of signaling msg from encoder to player
SIGNALLING_DATA_TYPE = 0 #either a signaling msg send by broadcast to mux svr or send by mux to player

AUDIO_DATA_TYPE_SPEEX = 9

SCREEN_FRAME_LAST_PACKET = 12
SCREEN_FRAME_PACKET = 13
VIDEO_FRAME_LAST_PACKET = 14
VIDEO_FRAME_PACKET = 15
MAX_DURATION_OF_ONE_ROUND = 6*60*60*90000 #Max DWORD (0xffffffff) milliseconds will only last 6 hours for  rtp timestamp with 90000 units
#MAX_DURATION_OF_ONE_ROUND = 10*60*90000  # test 10min rounds

RTP_HEADER_LEN = 12

MEDIA_TYPE_2_REQUEST_FLAG_MAP={
                VIDEO_FRAME_LAST_PACKET:'v',
                VIDEO_FRAME_PACKET:'v',                AUDIO_DATA_TYPE_SPEEX: 'a',
                SCREEN_FRAME_LAST_PACKET:'s',
                SCREEN_FRAME_PACKET:'s'
                }


def computeDigest(channel_key, svr_nonce, client_nonce):
        md5_obj = md5.new()
        md5_obj.update(channel_key + svr_nonce + client_nonce)
        return md5_obj.hexdigest()


def generateNonceStr():
    """
    generate a 128 bits of random bits string and turn to hex formated string
    """
    res = ""
    for i in range(32):
        res = res + random.choice("0123456789ABCDEF")
    return res

class ClientConnectionException(Exception):
    pass

class TypedMsgProtocol4(protocol.Protocol):
    """

    This is the version 4 of typed msg which implements the protocol as defined in
    https://docs.google.com/Doc?docid=0AXib23EVPZ13ZGZrMjNjMzlfNzAyZm01a3RyY2c&hl=en

    The key new feature in v4 is it will accept the interleaved RTP packet in addition to the leagcy
    v3 msg.

    Mux svr aceepting v4 protocol is backward compatible with v3 encoders

    To be noticed this version of mux will reject player and encoder talking previous version of protocol
    The magic is we use the first byte (0XF3,0xF2 or 0xF1) sent by the connected node(either encoder or player to
    tell whether it talks versionn 3 protocol
    """
    _SEND_BUFFER_CAP = 1000000 #we allow our send buffer to build up to 1M at most
    MAX_LENGTH = 65536 #assume the encoder will not send msg length > 16M bytes
    V3_MSG_HEADER_FLAG = 0xF3F3F3F3
    V3_MSG_HEAD_FORMAT = "<IBHI" #4 bytes flag| 1 byte type|2 bytes len|4 bytes timestamp
    V3_MSG_HEAD_LEN = struct.calcsize(V3_MSG_HEAD_FORMAT)
    RTP_INTERLEAVING_MSG_HEAD_FORMAT = ">BBH"
    RTP_INTERLEAVING_MSG_HEAD_LEN = struct.calcsize(RTP_INTERLEAVING_MSG_HEAD_FORMAT)
    BYTES_TO_RECEV_BEFORE_PARSE_MSG_HEAD = max(RTP_INTERLEAVING_MSG_HEAD_LEN, V3_MSG_HEAD_LEN )
    _RTP_INTERLEAVING_MSG = 0
    _V3_MSG = 1

    def __init__(self):
        #notice protocol.Protocol has no __init__()
        self._parsed_msg_header = {}
        self.recvd = ""
        self._msg_header_parsed_already = False

    def v3MsgReceived(self, type, timestamp, payload):
        """
        Override this.
        """
        raise NotImplementedError

    def rtpPacketReceived(self, rtp_ch_id, rtp_packet):
        raise NotImplementedError


    def lengthLimitExceeded(self, length):
        log.err("The length indicated in the msg header exceed's the allowed %d bytes"\
                                          % self.MAX_LENGTH)
        self.transport.loseConnection()

    def foundBrokenMsgHdrFlag(self):
        log.err("Can't find expected msg header flag. \
               Some bytes may get corrupted along the wire")
        self.transport.loseConnection()

    def _parseMsgHead(self):
        "return false to indicate error in the rcved ms header"
        if self.recvd[0]=='$':#its RTP interleaving msg
            self._parsed_msg_header['msg_type'] = self._RTP_INTERLEAVING_MSG
            headerFlag, self._parsed_msg_header['rtp_ch_id'], \
               self._parsed_msg_header['length'] = struct.unpack(self.RTP_INTERLEAVING_MSG_HEAD_FORMAT,
                                    self.recvd[:self.RTP_INTERLEAVING_MSG_HEAD_LEN])
            return True
        elif self.recvd[:4]=='\xf3\xf3\xf3\xf3': #its V3 msg
            self._parsed_msg_header['msg_type'] = self._V3_MSG
            headerFlag, self._parsed_msg_header['type'],\
               self._parsed_msg_header['length'], self._parsed_msg_header['timestamp'] = struct.unpack(self.V3_MSG_HEAD_FORMAT,
                                                self.recvd[:self.V3_MSG_HEAD_LEN])

            return True
        else:
            self.foundBrokenMsgHdrFlag()
            return False

    def dataReceived(self, recd):
        self.recvd = self.recvd + recd
        while len(self.recvd) >= self.BYTES_TO_RECEV_BEFORE_PARSE_MSG_HEAD:
            if not self._msg_header_parsed_already:
                if not self._parseMsgHead(): #error found in head
                    return
                self._msg_header_parsed_already = True

            length = self._parsed_msg_header['length']
            if length > self.MAX_LENGTH:
                self.lengthLimitExceeded(length)
                return

            if self._parsed_msg_header['msg_type']== self._RTP_INTERLEAVING_MSG:
                head_len = self.RTP_INTERLEAVING_MSG_HEAD_LEN
                if len(self.recvd) < (head_len + length):
                    break #wait until more data recved

                rtp_packet = self.recvd[head_len: head_len+length]

                self.recvd = self.recvd[head_len+length:]
                self.rtpPacketReceived(self._parsed_msg_header['rtp_ch_id'], rtp_packet)

            elif self._parsed_msg_header['msg_type']== self._V3_MSG:
                head_len = self.V3_MSG_HEAD_LEN
                if len(self.recvd) < (head_len + length):
                    break #wait until more data recved

                payload = self.recvd[head_len : head_len + length]
                self.recvd = self.recvd[head_len + length:]
                self.v3MsgReceived(self._parsed_msg_header['type'], self._parsed_msg_header['timestamp'], payload)

            self._msg_header_parsed_already = False

    def sendV3Msg(self, type, payload, timestamp=None):
        payload_len = len(payload)
        if  payload_len >= self.MAX_LENGTH:
            log.err("msg length could not exceed %s bytes\n" % self.MAX_LENGTH)
            return
        if timestamp is None:
            timestamp = int(time.time()*1000)&0xFFFFFFFF

        buf = struct.pack(self.V3_MSG_HEAD_FORMAT, self.V3_MSG_HEADER_FLAG, type, payload_len, timestamp)

        #check whether the internal buffer is building up, which is inidcatio of the problem in client connection
        if self.transport._tempDataLen>=self._SEND_BUFFER_CAP:
            raise ClientConnectionException("Client seems to be stalled")

        self.transport.write(buf)
        self.transport.write(payload)

    def sendV3SignalingMsg(self, payload):
        self.sendV3Msg(SIGNALLING_DATA_TYPE, payload)

    def sendRTPPacket(self, rtp_ch_id, rtp_packet):
        payload_len = len(rtp_packet)
        if  payload_len >= self.MAX_LENGTH:
            log.err("msg length could not exceed %s bytes\n" % self.MAX_LENGTH)
            return

        head = struct.pack(self.RTP_INTERLEAVING_MSG_HEAD_FORMAT, 0x24, rtp_ch_id, payload_len)#0x24='$'
        self.transport.write(head)
        self.transport.write(rtp_packet)

class MuxSvrProtocol4(TypedMsgProtocol4):
    """
    This is the hanlder for the connection to the mux svr. It will turned to be a encoder handler or a
    player handler after receiving the login cmd

    This implement the version 3 of the mux<->player/encoder protocol, and also backwards support version 1
    """
    STATUS_START = 0
    STATUS_AUTHENTICATED = 1

    CLIENT_TYPE_UNKNOWN = 0
    CLIENT_TYPE_ENCODER = 1
    CLIENT_TYPE_PLAYER = 2

    def __init__(self):
        TypedMsgProtocol4.__init__(self)
        self._client_type = self.CLIENT_TYPE_UNKNOWN
        self.channel = None
        self.status = self.STATUS_START


    def v3MsgReceived(self, type, timestamp, payload):
        """
        To be called by the base TypedMsgProtocol
        """
        if type == SIGNALLING_DATA_TYPE:
            self.handleSignalingMsg(payload)
        else:
            self.handleRcvedMediaPacket(type, timestamp, payload)

    def rtpPacketReceived(self, rtp_ch_id, packet):
        pass #discard any rtp packet before encoder passed authentication

    def handleSignalingMsg(self, payload):
        #to be noticed in v3 protocol, the signaling msg should have no trailing zero
        args=payload.split(";")
        cmd = args[0].lower()
        if self.status == self.STATUS_START:
            if cmd=="login_send":
                self._client_type = self.CLIENT_TYPE_ENCODER
                self.handleEncoderLogin(args)
            elif cmd=="login_recv":
                self._client_type = self.CLIENT_TYPE_PLAYER
                self.handlePlayerLogin(args)
            else:
                self.sendV3SignalingMsg("login_denied;unexpected_command")
                self.transport.loseConnection()
        elif self.status == self.STATUS_AUTHENTICATED:
            if cmd=="logout":
                self.handleClientLogout()

    def handleClientLogout(self):
        log.msg("Client requested logout")
        if self._client_type == self.CLIENT_TYPE_ENCODER:
            self._channel_relayer.encoderRequestToLogout(self)
        self.transport.loseConnection()
        #the muxer will be informed to drop the encoder/player automatically by the connectionLost handler

    def closeConnectionWithMsg(self, msg):
        """
        send a signnaling msg and then close the connection one second later (to allow the msg
        to be sent
        """
        self.sendV3SignalingMsg(msg)
        reactor.callLater(2, self.transport.loseConnection)

    def handleEncoderLogin(self, args):
        if len(args)<2:
            log.msg("encoder login msg syntax wrong %s. Drop the connection"%args.join(";"))
            self.transport.loseConnection()
        else:
            self.channel=args[1].lower()
            if (len(args)>=3 and args[2]=='drop_existing_encoders'):
                self._force_drop_existing_encoders = True
            else:
                self._force_drop_existing_encoders = False
            log.msg("A new encoder trying to connect from %s for channel %s"\
                    %(self.transport.getPeer(),self.channel))
            self._handleEncoderAuthentication()

    def _handleEncoderAuthentication(self):
        self._encoderAuthenticater = NewEncoderAuthenticater()
        ch_key = self.factory.channelKeyCache.lookupKey(self.channel)
        if ch_key==False:#no such channel at all!
            log.msg("No requested channel defined in DB. Drop Encoder from %s for channel %s "\
                %( self.transport.getPeer(),self.channel))
            self.closeConnectionWithMsg("login_denied;no_such_channel_exists")
        else:
            self._encoderAuthenticater.tryToAuthenticate(self.channel, ch_key, self).addCallback(self._handleEncoderAfterAuthenticationResultKnown)

    def _handleEncoderAfterAuthenticationResultKnown(self, authentication_passed):
        if not authentication_passed:
            log.msg("Authentication Failed. Drop Encoder from %s for channel %s "\
                %( self.transport.getPeer(),self.channel))
            self.sendV3SignalingMsg("login_denied;authentication_failed")
            self.transport.loseConnection()
        else:
            log.msg("Authentication passed for Encoder  connected from %s for channel %s"\
                %( self.transport.getPeer(),self.channel))

            channel_relayer = self.factory.getChannelRelayer(self.channel)
            if channel_relayer.isThereAlreadyAnEncoderConnected():
                if not self._force_drop_existing_encoders:
                    #reject the new encoder
                    existingEncoderAdr = channel_relayer.getExistingEncoderAdr()
                    log.msg("I will drop the new encoder as there is already an encoder %s in this channel",
                          existingEncoderAdr)
                    self.closeConnectionWithMsg("login_denied;channel_used_by_another_encoder;%s"%existingEncoderAdr)
                    #the connectionLost handler will set  self._encoderAuthenticater = None
                    return
                else:
                    #force the existing encoder to disconnect
                    prevEncoder = channel_relayer.getExistingEncoder()
                    prevEncoder.closeConnectionWithMsg("connection_rejected;a_new_encoder_login;%s"%
                                                                self.transport.getPeer())

            self._encoderAuthenticater = None#we could release it now

            #either no existing connection or existing connection has been rejected
            self.sendV3SignalingMsg("login_accepted")
            self.status = self.STATUS_AUTHENTICATED
            #late polymorph, mount encoder handler related methods
            self.handleRcvedMediaPacket = self._encoderHandleRcvedMediaPacket
            self.rtpPacketReceived = self._encoderHandleRcvedRTPPacket
            self._channel_relayer = channel_relayer
            self._channel_relayer.attachEncoder(self)


    def handlePlayerLogin(self, args):
        if len(args)!=3:
            log.msg("player login msg syntax wrong %s. Drop the connection"%args.join(":"))
            self.transport.loseConnection()
        else:
            self.channel,self.requested_submedia = args[1:3]
            self.channel = self.channel.lower()
            self.requested_submedia = self.requested_submedia.lower()
            log.msg("A new player trying to connect from %s for channel %s, requesting media %s"\
                    %( self.transport.getPeer(), self.channel, self.requested_submedia))

            self._channel_relayer = self.factory.getChannelRelayer(self.channel)
            self._channel_relayer.attachPlayer(self)

    def handleRcvedMediaPacket(self, type, timestamp, payload):
        pass


    def _encoderHandleRcvedMediaPacket(self, type, timestamp, payload):
        # from ppclass 3.0, encoder only send rtp packet ,so this fuc will not be used
        self._channel_relayer.processV3MediaPacket(type, timestamp, payload)

    def _encoderHandleRcvedRTPPacket(self, rtp_chd_id, packet):
        self._channel_relayer.processRTPPacket(rtp_chd_id,packet)

#    def _encoderHandleRcvedRTPInterleavedPacket(self, rtp_chd_id, packet):
#        self._channel_relayer.saveRtpInterleavedPacket(rtp_chd_id,packet)


    def playerFilterUnsubscribedTypeOfMediaPacket(self, type, timestamp, payload):
        #We only send the media packet with the type requested by the player
        #however, we always relay the end2end singlaing msg from encoder to player

        if (END_TO_END_SIGNALING_DATA_TYPE==type):
            self.sendV3Msg(type, payload, timestamp)
        elif not MEDIA_TYPE_2_REQUEST_FLAG_MAP.has_key(type):
            print "unknown media type rcved from the encoder"
        elif MEDIA_TYPE_2_REQUEST_FLAG_MAP[type] in self.requested_submedia :
            self.sendV3Msg(type, payload, timestamp)

    def connectionLost(self, reason):
        self._encoderAuthenticater = None#if the encoder connection lost during the authentication phase, we release the authenticater object
        if self._client_type==self.CLIENT_TYPE_ENCODER:
            if hasattr(self, '_channel_relayer') and self._channel_relayer is not None:
                self._channel_relayer.dettachEncoder(self)
        elif self._client_type==self.CLIENT_TYPE_PLAYER:
            if hasattr(self, '_channel_relayer') and self._channel_relayer is not None:
                self._channel_relayer.dettachPlayer(self)
        self._channel_relayer = None

class NewEncoderAuthenticater:
    """
    this object is responsible for the authentication of the new encoder
    To be noticed, this object will be responsible for the send of challenge msg and receive the challenge response.
    However, it  is up to the original protocol handler to send the client "login_accepted" or "login_denied" message.
    """
    MAX_CLIENT_RESPONSE_DELAY = 15

    def tryToAuthenticate(self, channel, ch_key, encoder_protocol_handler):
        """
        return a deferred which is called when the authentication result is known.
        The deferred will be called with True, if authetication passed. Otherwise, false
        """
        self.channel = channel
        self._channel_key = ch_key
        self._saved_protocol_handler = encoder_protocol_handler
        self._original_v3MsgReceived_method = None
        self.onAuthenticationFinished = Deferred()

        #we will take over the msg receiving method of the original protocol_handler until we know the authentication result
        self._original_v3MsgReceived_method = encoder_protocol_handler.v3MsgReceived
        encoder_protocol_handler.v3MsgReceived = self._waitingForChanllegeResponseMsg
        #alarm the watch dog
        self._watch_dog = reactor.callLater(self.MAX_CLIENT_RESPONSE_DELAY, self._noResponseFromClientInTime)

        self._sendAChallenge()
        return self.onAuthenticationFinished

    def _sendAChallenge(self):
        self._svr_nonce = generateNonceStr()
        self._saved_protocol_handler.sendV3SignalingMsg("challenge;%s"%self._svr_nonce)

    def _noResponseFromClientInTime(self):
        #client failed to send any response at all
        self._cleanup()
        self.onAuthenticationFinished.callback(False)

    def _waitingForChanllegeResponseMsg(self, type, timestamp, payload):
        #disalarm the watchdog as we had get some response from the client
        self._watch_dog.cancel()

        #The only valid msg we expect is the challenge_response
        res = False
        if type==SIGNALLING_DATA_TYPE:
            args=payload.split(";")
            if len(args)==3:
                cmd, client_nonce, digest_computed_by_client = args
                if cmd=='challenge_response':
                    if self._verifyDigest(client_nonce, digest_computed_by_client):
                        res = True

        self._cleanup()
        self.onAuthenticationFinished.callback(res)

    def _verifyDigest(self, client_nonce, digest_computed_by_client):
        return (digest_computed_by_client==computeDigest(self._channel_key, self._svr_nonce, client_nonce))

    def _cleanup(self):
        if self._original_v3MsgReceived_method is not None:
            #give back the msg receiving to the original one
            self._saved_protocol_handler.v3MsgReceived = self._original_v3MsgReceived_method
            self._original_v3MsgReceived_method = None
        self._saved_protocol_handler = None


class EncoderAlivenessWatchdog:
    """
    This object will monitor the associated  channel, if there is no any msg received from (any) encoders after
    a certain time, it deems the encoder's connection is experiencing problem, so it will inform the players in the channel
    of this issue.
    The channel relayer should call its update method on each received msg from encoder
    """
    ENCODER_ALIVE_EXPIRE_TIME = timedelta(seconds=10) #as the audio channel should send ms every 100ms or so, 10 seconds is large enought to tell wether the encoder has problem

    def __init__(self, channel_relayer):
        self._channel_relayer = channel_relayer
        self._alive_checker = LoopingCall(self._checkAlive)
        self._checker_interval = self.ENCODER_ALIVE_EXPIRE_TIME.seconds/2
        self._lastMsgTime = datetime.now()
        self._alarmed = False
        self._prev_state="INIT"

    def update(self):
        self._lastMsgTime = datetime.now()

    def alarm(self):
        "start monitoring"
        self._alarmed = True
        #if the previous status is "encoder dead" then first notify the relayer it has been recovered
        if self._prev_state=="ENCODER_FOUND_DIED":
            self._channel_relayer.onEncoderConnectionRecovered()
        self._prev_state = "ENCODER_ALIVE"
        self.update()#make sure to use the new timestamp for this round
        self._alive_checker.start(self._checker_interval)

    def disalarm(self):
        "stop monitoring"
        if self._alarmed:
            self._alarmed = False
            self._alive_checker.stop()

    def isAlarmed(self):
        return self._alarmed

    def _checkAlive(self):
        "will be called every 5 seconds to check whether encoder has sent some data"
        if (datetime.now()-self._lastMsgTime) > self.ENCODER_ALIVE_EXPIRE_TIME:
            if self._prev_state=="ENCODER_ALIVE":
                #the coonnection to encoder must exprience some problem
                self._channel_relayer.onEncoderConnectionDie()
                self._prev_state="ENCODER_FOUND_DIED"
        else:#did receive some packets in last interval
            if self._prev_state=="ENCODER_FOUND_DIED":
                self._channel_relayer.onEncoderConnectionRecovered()
                self._prev_state="ENCODER_ALIVE"

import xml.dom.minidom
import errno
import time
import xml.dom.minidom
import codecs

#http_streaming_web_root = "c:\\"

class IndexFileUpdater:
    def __init__(self,xmlname):
        self._xml_name = xmlname;
        impl = xml.dom.minidom.getDOMImplementation()
        self._document = impl.createDocument(None, None, None)
        self._root = self._document.createElement("Streamfragments")
        self._document.appendChild(self._root)
        self.fragment_delete_time_dic = {} # store when the fragment should be removed from XML, index by fragment name
        self._fragment_count = 10 # we only keep the latested 10 fragment in index fiel

    def _writexml(self):
        f = file(self._xml_name, 'w')
        writer = codecs.lookup('utf-8')[3](f)
        self._document.writexml(writer, encoding='utf-8')

    def _appendxml(self,timestamp_in_ms,screen_irtp_file_name,video_irtp_file_name,audio_irtp_file_name):
        document = self._document

        fragment = document.createElement("Fragment")
        fragment.setAttribute("timestamp_in_ms", timestamp_in_ms)

        self._root.appendChild(fragment)

        quality_level_node = document.createElement("QualityLevel")
        quality_level_node.setAttribute("level",'M')

        substream_audio_node = document.createElement("Substream")
        substream_audio_node.setAttribute("stream_id","A")
        substream_audio_node.appendChild(document.createTextNode(audio_irtp_file_name))

        substream_screen_node = document.createElement("Substream")
        substream_screen_node.setAttribute("stream_id","S")
        substream_screen_node.appendChild(document.createTextNode(screen_irtp_file_name))

        substream_video_node = document.createElement("Substream")
        substream_video_node.setAttribute("stream_id","V")
        substream_video_node.appendChild(document.createTextNode(video_irtp_file_name))


        quality_level_node.appendChild(substream_audio_node)
        quality_level_node.appendChild(substream_screen_node)
        quality_level_node.appendChild(substream_video_node)


        fragment.appendChild(quality_level_node)

    def updateXML(self,timestamp_in_ms,screen_irtp_file_name,video_irtp_file_name,audio_irtp_file_name):
        document = self._document
        fragment_count = 0
        timestamp = []
        if(len(document.childNodes) == 0):
            self._appendxml(timestamp_in_ms,screen_irtp_file_name,video_irtp_file_name,audio_irtp_file_name)
        else:
            for dom in document.childNodes: #streamfragment
                for i in dom.childNodes:#Fragment
                    if i.nodeName == 'Fragment':
                        fragment_count = fragment_count + 1
                        timestamp.append(i.getAttribute("timestamp_in_ms"))
                if  fragment_count >= self._fragment_count:
                    for fragment_node in dom.childNodes:
                        if fragment_node.nodeName == 'Fragment':
                            if fragment_node.getAttribute("timestamp_in_ms") == timestamp[0]:
                                for quality_node in fragment_node.childNodes:
                                    for substream_node in quality_node.childNodes:
                                        for streamid_node in substream_node.childNodes:
                                            self.fragment_delete_time_dic.setdefault(streamid_node.data,str(datetime.now())[0:19])
                                dom.removeChild(fragment_node)
                self._appendxml(timestamp_in_ms,screen_irtp_file_name,video_irtp_file_name,audio_irtp_file_name)

        self._writexml()



class StreamFragmenter:
    RTP_INTERLEAVING_MSG_HEAD_FORMAT = ">BBH"
    RTP_INTERLEAVING_MSG_HEAD_LEN = struct.calcsize(RTP_INTERLEAVING_MSG_HEAD_FORMAT)
    SCREEN_KEY_FRAME_FORMAT = "<B"
    RTP_HEAD_FORMAT = "<B"
    IRTP_FILE_ALIVE_TIME = timedelta(seconds=60)
    KEY_SCREEN_FIRST_PACKET_FLAG = 2
    def _mkdirs(self, newdir, mode=0777):
        try:
            os.makedirs(newdir, mode)
        except OSError, err:
            # Reraise the error unless it's about an already existing directory
            if err.errno != errno.EEXIST or not os.path.isdir(newdir):
                raise

    def __init__(self, channel):
        #log.msg( "InitStreamsFragmentSave !!! ")
        self.channel = channel
        self.fragment_index = -1;
        self._mkdirs(os.path.join(params.http_streaming_web_root,self.channel))
        self._screen_keyframe_has_come = False
        fragment_index_name = 'index.xml'
        self._fragment_index_full_path = os.path.join(params.http_streaming_web_root,channel,fragment_index_name)
        print self._fragment_index_full_path
        if os.path.exists(self._fragment_index_full_path):
            os.remove(self._fragment_index_full_path)
        self.index_file_updater = IndexFileUpdater(self._fragment_index_full_path)
        self._file_handle_dictionary = {}


    def unInitStreamsFragmentSave(self):
        #clean-up the dir
        for file in os.listdir(os.path.join(params.http_streaming_web_root,self.channel)):
            #log.msg("to remove from disk:" + os.path.join(params.http_streaming_web_root,self.channel,file))
            try:
                os.remove(os.path.join(params.http_streaming_web_root,self.channel,file))
            except:
                log.msg("can not remove " + os.path.join(params.http_streaming_web_root,self.channel) + " from disk")
                continue

        self.channel = None
        self.fragment_index = -1
        self._fragment_index_full_path = None
        self._finishCurrentFragment()
        self._file_handle_dictionary.clear()


    def _getNextFragmentIndex(self):
        self.fragment_index += 1
        if ( self.fragment_index == sys.maxint ):
            self.fragment_index = 0

    def saveFragment(self, ch_id, rtp_head_with_interleaveing_msg_head,rtp_packet):
        if (ch_id == SCREEN_RTP_INTERLEAVE_CH_ID ):
            rtp_len = len(rtp_packet)
            if(rtp_len > RTP_HEADER_LEN):
                #the 16th byte is the first byte of the screen payload
                screen_keyframe_flag = struct.unpack(self.SCREEN_KEY_FRAME_FORMAT, rtp_packet[12:13])
                if ( screen_keyframe_flag[0] & 0x7f == self.KEY_SCREEN_FIRST_PACKET_FLAG):#the final two bits=2 indicates the first packet of the key screen frame
                    if(self.fragment_index!=-1):
                        self._updateIndex(self._fragment_index_full_path)
                    else:
                        pass # do noting

                    self._finishCurrentFragment()# close the old file handles

                    self._screen_keyframe_has_come= True
                    self._startANewFragment()#open the new file handles
                    #log.msg( 'save screen from key frame in a new file, index is :' +  str(self.fragment_index))

        if (self._screen_keyframe_has_come == True):
            self._file_handle_dictionary[ch_id].write(rtp_head_with_interleaveing_msg_head)
            self._file_handle_dictionary[ch_id].write(rtp_packet[RTP_HEADER_LEN:])

    def _startANewFragment(self):
        self._getNextFragmentIndex()
        #log.msg("self.fragment_index: " + str(self.fragment_index))
        self._file_handle_dictionary[AUDIO_RTP_INTERLEAVE_CH_ID]=open(os.path.join(params.http_streaming_web_root, self.channel, "A_M_" + str(self.fragment_index)+ ".irtp"),"wb")
        self._file_handle_dictionary[VIDEO_RTP_INTERLEAVE_CH_ID]=open(os.path.join(params.http_streaming_web_root, self.channel, "V_M_" + str(self.fragment_index)+ ".irtp"),"wb")
        self._file_handle_dictionary[SCREEN_RTP_INTERLEAVE_CH_ID]=open(os.path.join(params.http_streaming_web_root, self.channel, "S_M_" + str(self.fragment_index)+ ".irtp"),"wb")


    def _finishCurrentFragment(self):
        if (self._screen_keyframe_has_come == True):
            for file_handle in self._file_handle_dictionary.values():
                file_handle.close()



    def _updateIndex(self,index_filename):
        timestamp_in_ms = str(time.time()*1000)
        screen_irtp_file_name = "S_M_"+str(self.fragment_index)+".irtp"
        video_irtp_file_name = "V_M_"+str(self.fragment_index)+".irtp"
        audio_irtp_file_name = "A_M_"+str(self.fragment_index)+".irtp"

        self.index_file_updater.updateXML(timestamp_in_ms, screen_irtp_file_name, video_irtp_file_name, audio_irtp_file_name)
        self._deleteFragment()

    def _deleteFragment(self):
        now = datetime.now()
        del_fragment_list = []
        for key in self.index_file_updater.fragment_delete_time_dic:
            value = self.index_file_updater.fragment_delete_time_dic[key]
            t = time.strptime(value, "%Y-%m-%d %H:%M:%S")
            stream_irtp_del_time = datetime(*t[:6])
#            stream_irtp_del_time = datetime.strptime(value,"%Y-%m-%d %H:%M:%S")
            if(now - stream_irtp_del_time > self.IRTP_FILE_ALIVE_TIME): # 60 seconds
                full_path = os.path.join(params.http_streaming_web_root,self.channel,key)
                if os.path.exists(full_path):
#                    log.msg( "remove : " + full_path)
                    os.remove(full_path)
                    del_fragment_list.append(key)

        for key in del_fragment_list:
            del self.index_file_updater.fragment_delete_time_dic[key]

class _RTPRelaySocketPortAllocater:
    """
    There will be a global instance of this class, responsible for assign UDP port range for each
    channel to use with its rtp relay function.
    Each range conains 8 consecutive UDP port start at an even one.

    And during the life cycle of the mux svr, this assignment is fixed, i.e, a channel will always
    get the same port range when the channel is re-created
    """

    _channel_port_assignment = {} #each item will be like "ch_id:base_port"
    _next_available_port = MUX_TO_DSS_RTP_PORT_RANGE_START

    def getAssignedBaseUdpPortForChannel(self, channel_id):
        """
        return the base udp port for the 8 ports assigend for the channel. must be an even one
        """
        if self._channel_port_assignment.has_key(channel_id):
            base_port = self._channel_port_assignment[channel_id]
        else:
            base_port = self._next_available_port
            self._next_available_port+=8
            self._channel_port_assignment[channel_id] = base_port

        assert (base_port%2)==0 #base_port will be used as RTP port which should be even
        return base_port


_rtp_relay_socket_port_allocator = _RTPRelaySocketPortAllocater()


class ChannelRelayer:
    """
    Change in V4. Now if the enable_rtp_relay global param is set, the ChannelRelay is also responsible for generating SDP for the channel
    and push RTP packets to the port according to the SDP

    The rtp relay socket pair is created when ChannelRelayer is created.
    However, the SDP file is created on Encoder login and deleted on Encoder disconnect, thus
    we can know whether the encoder is on by checking the existence of the sdp file

    """
    audio_time_units = 16 #audio rtp time units 16000, because the timestamp encoder sended is millisecond units, so 16000/1000 = 16
    video_screen_time_units = 90


    def onEncoderConnectionDie(self):
        """The encoder socket was never closed but somehow no
        packet received from the encoder recently, so we think it died abnormally
        notice this should only be called once when encoder die"""
        log.msg("encoder on channel %s seems to be dead. I will inform the downstream player and update the channel onair status in DB"%self.channel)
        self._encoder_problem_informer = LoopingCall(self._informPlayersAboutEncoderConnectionProblem)
        INFORM_MSG_INTERVAL=5
        self._encoder_problem_informer.start(INFORM_MSG_INTERVAL)
        self._markChannelOnAirStatusInDB(False)

    def onEncoderConnectionRecovered(self):
        """Since last onEncoderConnectionDie, we got some packets from encoder"""
        log.msg("encoder on channel %s seems to be alive again. I will update the channel onair status in DB"%self.channel)
        self._markChannelOnAirStatusInDB(True)
        if self._encoder_problem_informer:
            self._encoder_problem_informer.stop()

    def __init__(self, channel):
        self.channel = channel
        self.encoder_handler = None
        self.player_handler_list = []
        self._empty_since = None
        self._encoderAlivenessWatchdog = EncoderAlivenessWatchdog(self)
        self._RTP_INTERLEAVING_MSG_HEAD_FORMAT = ">BBH"

        if params.enable_rtp_relay:
            self._sdp_file_path = None
            self._dst_rtp_rtcp_port = {} #rtcp is assumed to be the 1 higher than the corresponding rtp port
            self._prepareRTPRelaySockets()

        if params.enable_http_streaming:
            self._stream_fragmenter = StreamFragmenter(channel)

    def _prepareRTPRelaySockets(self):
        #channel with ID like XXXXXX00 will use port 0-7
        #FIXME: there is a risk that if the uid range is bigger than 100 than there be overlap
        base_udp_port = _rtp_relay_socket_port_allocator.getAssignedBaseUdpPortForChannel(self.channel)
        assert (base_udp_port%2)==0 #rtp port should be even one
        src_rtp_port = base_udp_port
        src_rtcp_port = base_udp_port +1
        dst_audio_rtp_port = base_udp_port +2
        dst_audio_rtcp_port = base_udp_port +3
        dst_video_rtp_port = base_udp_port +4
        dst_video_rtcp_port = base_udp_port +5
        dst_screen_rtp_port = base_udp_port +6
        dst_screen_rtcp_port = base_udp_port +7

        self._rtp_src_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        #log.msg("rtp_port:"+str(src_rtp_port)+"channel:"+self.channel)
        self._rtp_src_socket.bind(('0.0.0.0', src_rtp_port))


        self._rtcp_src_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._rtcp_src_socket.bind(('0.0.0.0', src_rtcp_port))

        self._dst_rtp_rtcp_port[AUDIO_RTP_INTERLEAVE_CH_ID] = dst_audio_rtp_port
        self._dst_rtp_rtcp_port[AUDIO_RTCP_INTERLEAVE_CH_ID] = dst_audio_rtcp_port
        self._dst_rtp_rtcp_port[VIDEO_RTP_INTERLEAVE_CH_ID] = dst_video_rtp_port
        self._dst_rtp_rtcp_port[VIDEO_RTCP_INTERLEAVE_CH_ID] = dst_video_rtcp_port
        self._dst_rtp_rtcp_port[SCREEN_RTP_INTERLEAVE_CH_ID] = dst_screen_rtp_port
        self._dst_rtp_rtcp_port[SCREEN_RTCP_INTERLEAVE_CH_ID] = dst_screen_rtcp_port

    def _createSDP(self):
        audio_rtp_dst_port = self._dst_rtp_rtcp_port[AUDIO_RTP_INTERLEAVE_CH_ID]
        video_rtp_dst_port = self._dst_rtp_rtcp_port[VIDEO_RTP_INTERLEAVE_CH_ID]
        screen_rtp_dst_port = self._dst_rtp_rtcp_port[SCREEN_RTP_INTERLEAVE_CH_ID]

        sdp_path = os.path.join(SDP_FILE_FOLDER, self.channel+'.sdp')
        #del old file if exists
        if os.path.exists(sdp_path):
            os.remove(sdp_path)

        tmp = """v=0
o=- 14994548490988111623 14994548490988111623 IN IP4 sjtu-p2p
s=%s
i=N/A
c=IN IP4 127.0.0.1
t=0 0
a=tool:vlc 0.9.9a
a=recvonly
a=type:broadcast
a=charset:UTF-8
m=audio %d RTP/AVP 96
b=RR:0
a=rtpmap:96 SPEEX/16000
m=video %d RTP/AVP 97
b=RR:0
a=rtpmap:97 H264/90000
a=fmtp:97 packetization-mode=1;profile-level-id=4d4033;sprop-parameter-sets=Z01AM5ZkFidCAAADAAIAAAMAFR4wZUA=,aO48gA==
m=screen %d RTP/AVP 98
b=RR:0
a=rtpmap:98 SJSC2/90000
"""%(self.channel, audio_rtp_dst_port, video_rtp_dst_port, screen_rtp_dst_port)
        file(sdp_path, 'w').write(tmp)
        self._sdp_file_path = sdp_path

    def _deleteSDPIfExists(self):
        #delete sdp file we created and the rtp sockets
        if self._sdp_file_path:
            os.remove(self._sdp_file_path)
            self._sdp_file_path = None


    def cleanup(self):
        "release any resource created for the channel here"
        if params.enable_rtp_relay:
            self._deleteSDPIfExists()
            self._closeRTPSocketsIfExists()
        if params.enable_http_streaming:
            self._stream_fragmenter.unInitStreamsFragmentSave()

    def _closeRTPSocketsIfExists(self):
        if self._rtp_src_socket:
            self._rtp_src_socket.close()
            self._rtp_src_socket=None

        if self._rtcp_src_socket:
            self._rtcp_src_socket.close()
            self._rtcp_src_socket=None

    def _translateRTP2MSGV3(self, rtp_ch_id, packet):
        timestamp = struct.unpack('>I', packet[4:8])[0]
        if rtp_ch_id == AUDIO_RTP_INTERLEAVE_CH_ID :
            type =     _SPEEX
        elif rtp_ch_id == VIDEO_RTP_INTERLEAVE_CH_ID:
            type = VIDEO_FRAME_LAST_PACKET
        else:
            mark = (struct.unpack('>B', packet[1:2])[0]) >> 7
            #pt = (struct.unpack('>B', packet[1:2])[0]) & 0x7f
            #log.msg("mark: "+str(mark)+"pt: "+str(pt))
            if mark == 1: # this is the last packet of one frame
                type = SCREEN_FRAME_LAST_PACKET
            else:
                type = SCREEN_FRAME_PACKET
        #log.msg("timestamp:" + str(timestamp)+ "ch_id: "+str(rtp_ch_id))
        payload = packet[RTP_HEADER_LEN:]
        return (type,timestamp,payload)
#        self.relayMediaPacket(type, timestamp, payload)



    def processRTPPacket(self, rtp_ch_id, packet):
        def reCreateRTPHead(self, rtp_ch_id, packet):
            #recreate rtp head with timestampe changed
            rtp_head = struct.unpack('>III', packet[0:RTP_HEADER_LEN])
            timestamp = rtp_head[1]
            if rtp_ch_id == AUDIO_RTP_INTERLEAVE_CH_ID :
                timestamp *= self.audio_time_units
                timestamp_with_sv_time_units = timestamp/self.audio_time_units*self.video_screen_time_units
                rounds = timestamp_with_sv_time_units / MAX_DURATION_OF_ONE_ROUND
                timestamp_with_sv_time_units = timestamp_with_sv_time_units - rounds * MAX_DURATION_OF_ONE_ROUND
                timestamp = timestamp_with_sv_time_units/self.video_screen_time_units * self.audio_time_units
            else:
                timestamp *= self.video_screen_time_units
                rounds = timestamp / MAX_DURATION_OF_ONE_ROUND
                timestamp -= rounds * MAX_DURATION_OF_ONE_ROUND
            return struct.pack(">III",rtp_head[0],timestamp,rtp_head[2])

        self._encoderAlivenessWatchdog.update()

        type,timestamp,payload = self._translateRTP2MSGV3(rtp_ch_id,packet)

        self.relayMediaPacket(type, timestamp, payload)

        if params.enable_rtp_relay or  params.enable_http_streaming:
            rtp_new_head = reCreateRTPHead(self,rtp_ch_id,packet)

        if params.enable_rtp_relay:
            self._relayRTPPacket(rtp_ch_id, rtp_new_head,packet)
        if params.enable_http_streaming:
            self._saveIRTPPacket(rtp_ch_id, rtp_new_head,packet)


    def _saveIRTPPacket(self, rtp_ch_id, rtp_new_head, packet):
        payload_len = len(packet)

        rtp_interleaveing_msg_head = struct.pack(self._RTP_INTERLEAVING_MSG_HEAD_FORMAT, 0x24, rtp_ch_id, payload_len)#0x24='$'
        rtp_head_with_interleaveing_msg_head = rtp_interleaveing_msg_head + rtp_new_head
        self._stream_fragmenter.saveFragment(rtp_ch_id, rtp_head_with_interleaveing_msg_head, packet)

    def _relayRTPPacket(self, rtp_ch_id, rtp_new_head, packet):
        if rtp_ch_id%2==0:
            src_socket = self._rtp_src_socket
        else:
            src_socket = self._rtcp_src_socket

        dst_port = self._dst_rtp_rtcp_port[rtp_ch_id]

        new_packet = rtp_new_head+packet[RTP_HEADER_LEN:]

        src_socket.sendto(new_packet, (DSS_SVR_ADR, dst_port))

    def isThereAlreadyAnEncoderConnected(self):
        return (self.encoder_handler is not None)

    def getExistingEncoderAdr(self):
        if self.encoder_handler is None:
            return "No existing encoder"
        else:
            return self.encoder_handler.transport.getPeer()

    def getExistingEncoder(self):
        if self.encoder_handler is None:
            return None
        else:
            return self.encoder_handler

    def attachEncoder(self, protocolHandler):
        log.msg("Encoder (%s for channel %s) attached"\
                %(protocolHandler.transport.getPeer(), self.channel))

        self.encoder_handler=protocolHandler

        if params.enable_rtp_relay:
            self._createSDP()

        self._markChannelOnAirStatusInDB(True)

        if not self._encoderAlivenessWatchdog.isAlarmed():
            self._encoderAlivenessWatchdog.alarm()#no matter which encoder is attached, we alarm the watch dog

    def attachPlayer(self, protocolHandler):
        log.msg("Player (%s for channel %s) attached"\
                %(protocolHandler.transport.getPeer(), self.channel))

        self.player_handler_list.append(protocolHandler)
        self._markChannelAsUnempty()

    def dettachEncoder(self, protocolHandler):
        #to be noticed the detachEncoder will invoked both on explicit logout initiated byt the
        #encoder and other unexpected connection close. So we should not disalrm the watch dog here!
        log.msg("Encoder (%s for channel %s) dropped"\
                %(protocolHandler.transport.getPeer(), self.channel))

        #notice that current self.encoder_handler might not be the protocolHandler
        #considering that the dettaching encoder is actually kicked off by a encoder with force_login parameter
        #in this case we should not delete sdp
        if  self.encoder_handler==protocolHandler:
            self.encoder_handler = None
            self._markChannelEmptyrTimestampIfBecomeEmpty()
            if params.enable_rtp_relay:
                self._deleteSDPIfExists()
            self._markChannelOnAirStatusInDB(False)


    def dettachPlayer(self, protocolHandler):
        log.msg("Player (%s for channel %s) dropped"\
                %(protocolHandler.transport.getPeer(), self.channel))

        self.player_handler_list.remove(protocolHandler)
        self._markChannelEmptyrTimestampIfBecomeEmpty()

    def processV3MediaPacket(self, type, timestamp, payload):
        self._encoderAlivenessWatchdog.update()
        self.relayMediaPacket(type, timestamp, payload)

    def relayMediaPacket(self, type, timestamp, payload):
        for player_handler in self.player_handler_list:
            try:
                player_handler.playerFilterUnsubscribedTypeOfMediaPacket(type, timestamp, payload)
            except ClientConnectionException, e:
                log.err("I have too many backlog data to send. The client seems to be stalled. I will drop it")
                #FIXME. A hack to cause loseConnection to shutdown socket immediately. otherwise
                #the loseConnection will try to write all pending data first, which makes no sense for our case
                #however, to be noticed, the socket will not be really closed at the stack level, if the remote peer
                #failed to ACK to our FIN TCP request, so there are likely many haning zombbie tcp connection left
                player_handler.transport._writeDisconnected=True
                player_handler.transport.loseConnection()


    def encoderRequestToLogout(self, encoder):
        #notice that we will leave the dettachEncoder which will be called eventually to
        #reset the self.encoder_handler
        self._encoderAlivenessWatchdog.disalarm()
        self.broadcastSignallingMsgToPlayers("notice:session_stop")

    def broadcastSignallingMsgToPlayers(self, msg):
        for player_handler in self.player_handler_list:
            player_handler.sendV3SignalingMsg(msg)

    def _informPlayersAboutEncoderConnectionProblem(self):
        self.broadcastSignallingMsgToPlayers('notice:mux_encoder_connection_problem')

    def _markChannelAsUnempty(self):
        self._empty_since = None

    def _markChannelEmptyrTimestampIfBecomeEmpty(self):
        if self.encoder_handler is None and self.player_handler_list==[]:
            self._empty_since = datetime.now()

    def getEmptySince(self):
        """
        return None if not empty
        """
        if self.player_handler_list==[] and self.encoder_handler is None and self._empty_since is not None:
            return self._empty_since
        else:
            return None

    def _markChannelOnAirStatusInDB(self, on_air):
        conn = MySQLdb.connect (host = params.db_svr,
                                user = "PPClassAdmin",
                                passwd = params.db_passwd,
                                db = "PPClassDB",
                                charset="utf8")

        db_cursor = conn.cursor(MySQLdb.cursors.DictCursor)
        if on_air:
            state = "true"
        else:
            state = "false"
        db_cursor.execute("UPDATE ppclass_admin_channel SET on_air_status=%s WHERE uid='%s'"%(state,self.channel))
        conn.commit()
        conn.close()


#The design of objec relationship
#Muxer object is the global container for the ChannelRelayer objects and manges the lifecycle of the ChannelRelayer.
# However, It does not particiapte the encoder/player connection mangement and media packet relay.
#Each encoder/player connection object(protocol handler object) will refer to its associated ChannelRelayer

class Muxer(protocol.Factory):
    protocol = MuxSvrProtocol4
    #delete empty entries after they become empty for at most 60 seconds
    FLUSH_EMPTY_ENTRY_TIME_INTERVALE = timedelta(seconds=60)

    def __init__(self):
        self._channel_relayers = {} #looks like {channeld_id1:ChannelRelayerObj1,....}
        self.houseKeeper = LoopingCall(self._flushStaleEmptyChannelEntry)
        self.houseKeeper.start(self.FLUSH_EMPTY_ENTRY_TIME_INTERVALE.seconds)
        self.channelKeyCache = ChannelKeyCache()

    def getChannelRelayer(self, channel):
        if channel not in self._channel_relayers:
            self._channel_relayers[channel]=ChannelRelayer(channel)

        return self._channel_relayers[channel]

    def _flushStaleEmptyChannelEntry(self):
        for channel in self._channel_relayers.keys():
            empty_since_time = self._channel_relayers[channel].getEmptySince()
            if empty_since_time is not None and (datetime.now()-empty_since_time)>= self.FLUSH_EMPTY_ENTRY_TIME_INTERVALE:
                self._channel_relayers[channel].cleanup()
                del self._channel_relayers[channel]
                log.msg("entry for empty channel %s is flushed"%channel)

class ChannelKeyCache:
    """
    This class function as a cache to the channel table in the DB for better performance.
    It will go to the database every 5 minutes to have the cache updated
    """
    CACHE_REFRESH_INTERVAL = 300

    def __init__(self):
        """
        if use_fake_db is True, ignore the dbSvr parameter and use a fake DB instead. Intended for local debuggin
        """
        if params.use_fake_db:
            self.populateWithFakeChannelKeyData()
        else:
            self._refreshCache = self._refreshCache_PPClassMode
            self._refreshCache()
            LoopingCall(self._refreshCache).start(self.CACHE_REFRESH_INTERVAL, now=False)

    def populateWithFakeChannelKeyData(self):
        self._cache = { '550e8400-e29b-41d4-a716-446655441005':'ABCDEFGH',
                        '550e8400-e29b-41d4-a716-446655441004':'ABCDEFGH',
                        '550e8400-e29b-41d4-a716-446655441006':'zqzhang',
                       }

    def _refreshCache_PPClassMode(self):
        conn = MySQLdb.connect (host = params.db_svr,
                                user = "PPClassAdmin",
                                passwd = params.db_passwd,
                                db = "PPClassDB",
                                charset="utf8")

        db_cursor = conn.cursor(MySQLdb.cursors.DictCursor)
        db_cursor.execute("select ppclass_admin_channel.uid, ppclass_admin_channel.key from ppclass_admin_channel")
        self._cache = {}
        for row in db_cursor.fetchall():
            self._cache[row['uid']]=row['key']
        log.msg("cache refreshed from database")

#    def _refreshCache_189CastMode(self):
#        conn = MySQLdb.connect (host = params.db_svr,
#                                user = "The189CastAdmin",
#                                passwd = params.db_passwd,
#                                db = "The189CastDB",
#                                charset="utf8")
#
#        db_cursor = conn.cursor(MySQLdb.cursors.DictCursor)
#        db_cursor.execute("select the189cast_admin_channel.uid, the189cast_admin_channel.key from the189cast_admin_channel")
#        self._cache = {}
#        for row in db_cursor.fetchall():
#            self._cache[row['uid']]=row['key']
#        log.msg("cache refreshed from database")

    def lookupKey(self, channel_uid):
        return self._cache.get(channel_uid, False)

def run():
    pass


if __name__ == '__main__':
    print "Mux server. Supports protocol version 4 . Release Tag=%s"%RELEASE_TAG
    print "use --help to get possible commandline options"
    parser = optparse.OptionParser(usage="Mux server of PPClasss")
    parser.add_option("-p", "--port", type="int", dest="port", default=51111, help="TCP port to listen on")
    parser.add_option("-l", "--log", action="store_true", dest="log", default=False, help="enable log to file")
    parser.add_option("-f", "--use_fake_db", action="store_true", dest="use_fake_db", default=False, help="set to use a fake channel ...0004/...0005 with key (ABCDEFGH)  without accessing a DB. Used mainly for debug")
    parser.add_option("-d", "--dbsvr", dest="db_svr", default="localhost", help="specify where the DB svr resides (to retrieve the channel key)")
    passwd = "dnf94la"
    parser.add_option("-s", "--dbpasswd", dest="db_passwd", default=passwd, help="specify the password of the DB user")
    parser.add_option("-r", "--enable_rtp_relay", action="store_true", dest="enable_rtp_relay", default=False, help="specify to enable rtp relay and sdp generation")
    parser.add_option("-b", "--enable_http_streaming", action="store_true", dest="enable_http_streaming", default=False, help="specify to enable irtp packet to save")
#    parser.add_option("-t", "--for_3rd_party_usage", action="store_true", dest="for_3rd_party_usage", default=False, help="if not set, the mux svr is intended for onlinesjtu; if set it is for 3rd party usage. These two modes will use different database")
    parser.add_option("-w", "--http_streaming_web_root",dest="http_streaming_web_root",default = "/var/www/ppclass",help="http_streaming_web_root")
    global params
    (params, restArgs) = parser.parse_args()

    if params.log:
        log.startLogging(logfile.DailyLogFile( 'MuxSvr.log', '.'))
    else:
        log.startLogging(sys.stdout)

    muxer = Muxer()#Muxer is a subclass of clientFactory
    reactor.listenTCP(params.port, muxer)
    log.msg("Start listening on port %d "%params.port)

    reactor.run()